package com.bb.blog.security.config;


import com.bb.blog.security.component.SecurityPasswordTokenGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 配置类
 */
@Configuration
@Import(WebSecurityConfig.class)

public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 认证code 的有效时间 默认300秒
     */
    private static Long VALID_TIME = 5 * 60L;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private ClientDetailsService myClientService;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    @Autowired
    private TokenStore tokenStore;
    @Qualifier("bbTokenService")
    @Autowired
    private AuthorizationServerTokenServices tokenServices;

    @Value(value = "${auth.code.validTime:300}")
    public static void setValidTime(Long validTime) {
        VALID_TIME = validTime;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(myClientService);

    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.
            authenticationManager(authenticationManager).
            allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST).
            authorizationCodeServices(authorizationCodeServices(redisTemplate)).
            tokenServices(tokenServices).
            tokenGranter(tokenGranter(endpoints));

    }


    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
    }


    @Bean
    AuthorizationCodeServices authorizationCodeServices(RedisTemplate<Object, Object> redisTemplate) {
        return new RandomValueAuthorizationCodeServices() {
            private String getKey(String code) {
                return AuthorizationCodeServices.class.toString() + code;
            }

            @Override
            protected void store(String code, OAuth2Authentication authentication) {
                String key = getKey(code);
                redisTemplate.opsForValue().set(key, authentication, VALID_TIME, TimeUnit.SECONDS);
            }

            @Override
            protected OAuth2Authentication remove(String code) {
                String key = getKey(code);
                Object o = redisTemplate.opsForValue().get(key);
                redisTemplate.delete(key);
                return (OAuth2Authentication) o;
            }
        };
    }


    public TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {

        TokenGranter tokenGranter = new TokenGranter() {
            private CompositeTokenGranter delegate;

            @Override
            public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
                if (delegate == null) {
                    delegate = new CompositeTokenGranter(getDefaultTokenGranters(endpoints));
                }
                return delegate.grant(grantType, tokenRequest);
            }
        };
        return tokenGranter;
    }

    private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) {
        AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
        AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
        OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();
        ClientDetailsService clientDetailService = endpoints.getClientDetailsService();


        List<TokenGranter> tokenGranters = new ArrayList<>();
        // 添加授权码模式
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetailService, requestFactory));
        // 添加刷新令牌的模式
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetailService, requestFactory));
        // 添加隐式授权模式
        tokenGranters.add(new ImplicitTokenGranter(tokenServices, clientDetailService, requestFactory));
        // 添加客户端模式
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetailService, requestFactory));
        // 添加自定义授权模式（实际是密码模式的复制）
//        tokenGranters.add(new WeChatAbstractTokenGranter(tokenServices, clientDetailsService, requestFactory));
        if (authenticationManager != null) {
            // 添加密码模式
            tokenGranters.add(new SecurityPasswordTokenGranter(authenticationManager, tokenServices, clientDetailService, requestFactory));
        }
        return tokenGranters;
    }

}
